package com.yugao.fintech.antelope.base.log;

import com.alibaba.fastjson2.JSON;
import com.yugao.fintech.antelope.base.security.utils.SecurityUtils;
import com.yugao.fintech.antelope.base.log.enums.BusinessStatus;
import com.yugao.fintech.antelope.base.log.event.OperateLogEvent;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContextHolder;
import com.yugao.fintech.framework.assistant.utils.StringUtils;
import com.yugao.fintech.framework.core.http.HttpRequestServletManager;
import com.yugao.fintech.framework.core.utils.IpUtils;
import com.yugao.fintech.framework.core.utils.ServletUtils;
import com.yugao.fintech.framework.core.utils.SpringUtils;
import com.yugao.fintech.framework.log.utils.MDCTraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 操作日志记录处理
 *

 */
@Slf4j
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class OperateLogAspect {

    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint point, OperateLog operateLog) throws Throwable {
        Object saveResult = null;
        OperateLogInfo.OperateLogInfoBuilder builder = OperateLogInfo.builder();
        StopWatch watch = StopWatch.createStarted();
        LogTag logTag = point.getTarget().getClass().getAnnotation(LogTag.class);
        String tagName = Optional.ofNullable(logTag).map(LogTag::name).orElse("");
        try {
           Object result = point.proceed();
           if (operateLog.isSaveOutData()) {
               saveResult = result;
           }
           return result;
       } catch (Throwable e) {
           builder.status(BusinessStatus.FAIL.ordinal())
                   .errorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
           throw e;
       } finally {
           HttpServletRequest request = ServletUtils.getRequest().orElseThrow(() -> new RuntimeException("request is null"));
           // 请求的地址
           String ip = IpUtils.getRequestIp(new HttpRequestServletManager(request));

           // 设置方法名称
           String className = point.getTarget().getClass().getName();
           String methodName = point.getSignature().getName();

           builder.status(BusinessStatus.OK.ordinal()).ip(ip)
                   .url(request.getRequestURI())
                   .responseData(JSON.toJSONString(saveResult))
                   .costTime(watch.getTime(TimeUnit.MILLISECONDS))
                   // 设置请求方法
                   .classMethod(className + "." + methodName + "()")
                   .httpMethod(request.getMethod());

           OperateLogInfo operateLogInfo = builder.build();
           operateLogInfo.setUserName(SecurityUtils.getLoginUser().getUserName());
           // 处理设置注解上的参数
           getControllerMethodDescription(point, tagName, operateLog, operateLogInfo);
           // 设置请求标识: 由于日志中一般都会携带traceId, 所以这里就不将traceId打印到日志中了
           operateLogInfo.setTraceId(MDCTraceUtils.getTraceId());
           operateLogInfo.setTenantId(TenantContextHolder.get().getTenantId());
           // 保存数据
           if (operateLog.isSaveDb()) {
               SpringUtils.getContext().publishEvent(new OperateLogEvent(operateLogInfo));
           }
       }
    }


    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log        日志
     * @param operateLogInfo 操作日志
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, String tagName, OperateLog log, OperateLogInfo operateLogInfo) throws Exception {
        // 设置action动作
        operateLogInfo.setBusinessType(log.businessType().name());
        // 设置标签
        operateLogInfo.setTag(tagName);
        // 设置摘要
        operateLogInfo.setSummary(log.summary());

        // 设置操作人类别
        operateLogInfo.setOperatorType(log.operatorType().ordinal());
        // 是否需要保存request，参数和值
        if (log.isSaveInData()) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, operateLogInfo);
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operateLogInfo 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, OperateLogInfo operateLogInfo) throws Exception {
        // 参数值
        Object[] argsValue = joinPoint.getArgs();
        // if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
        String params = argsArrayToString(argsValue);
        operateLogInfo.setRequestData(StringUtils.substring(params, 0, 2000));
        // }
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private OperateLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(OperateLog.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception e) {
                        log.error("参数拼接异常: {}", e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}
